//+------------------------------------------------------------------+
//|                    DXUtils.mqh DirectX 3D 工具函数头文件         |
//|                版权所有 2000-2025, MetaQuotes Ltd.              |
//|                        官方网址: https://www.mql5.com            |
//+------------------------------------------------------------------+
#property copyright "Copyright 2000-2025, MetaQuotes Ltd."  // 版权声明
#property link      "https://www.mql5.com"                 // 官方链接

#include "DXMath.h"  // 引入 DirectX 数学工具类（向量、矩阵、颜色计算）
#include "DXData.h"  // 引入 DirectX 数据结构（顶点、颜色等）

//+------------------------------------------------------------------+
//| Wavefront OBJ 文件数据类型宏定义                                  |
//| 标识 OBJ 文件中不同类型的几何数据行，用于解析模型文件             |
//+------------------------------------------------------------------+
#define OBJ_DATA_UNKNOWN  0  // 未知数据类型（跳过解析）
#define OBJ_DATA_V        1  // 顶点位置数据（对应 OBJ 文件中的 "v" 行）
#define OBJ_DATA_VT       2  // 纹理坐标数据（对应 OBJ 文件中的 "vt" 行）
#define OBJ_DATA_VN       3  // 顶点法向量数据（对应 OBJ 文件中的 "vn" 行）
#define OBJ_DATA_F        4  // 面数据（对应 OBJ 文件中的 "f" 行，定义三角形/四边形）

//+------------------------------------------------------------------+
//| OBJFaceType 结构体                                                |
//| 功能：存储 Wavefront OBJ 文件中单个面的索引信息（关联顶点、纹理、法向量）|
//| 适用场景：OBJ 模型解析时，临时存储面的几何关联关系                |
//+------------------------------------------------------------------+
struct OBJFaceType
  {
   int               total;  // 面的顶点数量（3=三角形，4=四边形，不支持其他数量）
   int               v[4];   // 顶点位置索引数组（对应 OBJ 文件中 "f" 行的第一个值，最多 4 个顶点）
   int               t[4];   // 纹理坐标索引数组（对应 OBJ 文件中 "f" 行的第二个值，与顶点数量一致）
   int               n[4];   // 顶点法向量索引数组（对应 OBJ 文件中 "f" 行的第三个值，与顶点数量一致）
  };

//+------------------------------------------------------------------+
//| 工具函数：反转网格绕序（适配左右手坐标系转换）                    |
//| 模板约束：TVertex 必须包含 DXVector4 normal（法向量）和 DXVector2 tcoord（纹理坐标）成员 |
//+------------------------------------------------------------------+
/**
 * @brief 反转网格的顶点绕序与法向量方向（解决左右手坐标系渲染正反面颠倒问题）
 * @tparam TVertex 顶点数据类型（需包含 normal 法向量和 tcoord 纹理坐标成员）
 * @param vertices [in/out] 顶点数据数组（将修改法向量方向和纹理坐标）
 * @param indices [in/out] 索引数据数组（将反转三角形顶点顺序，改变绕序）
 * @note 核心逻辑：
 *       1. 反转所有顶点的法向量（x/y/z 分量取反），修正光照计算方向；
 *       2. 翻转纹理坐标的 U 分量（1 - tcoord.x），避免纹理镜像；
 *       3. 按三角形分组反转索引顺序（每 3 个索引为一组，交换首尾元素），改变面的绕序
 */
template <typename TVertex>
void DXInverseWinding(TVertex &vertices[],uint &indices[]);

//+------------------------------------------------------------------+
//| 工具函数：解析 OBJ 文件原始数据（提取顶点、纹理、法向量、面信息）  |
//+------------------------------------------------------------------+
/**
 * @brief 从 Wavefront OBJ 文件解析原始几何数据（不生成渲染用顶点/索引，仅提取基础信息）
 * @param filename [in] OBJ 文件路径（绝对路径或相对于 MQL5 目录的相对路径）
 * @param v_positions [out] 顶点位置数组（存储解析出的所有顶点 XYZ 坐标，w=1.0）
 * @param v_tcoords [out] 纹理坐标数组（存储解析出的所有纹理 UV 坐标）
 * @param v_normals [out] 顶点法向量数组（存储解析出的所有顶点法向量，w=0.0）
 * @param faces [out] 面信息数组（存储每个面的顶点/纹理/法向量索引关联关系）
 * @param show_debug [in] 调试信息开关（true=输出解析日志，false=不输出）
 * @return bool - 操作结果：true 表示解析成功（文件读取+数据提取完成），false 表示失败（文件不存在/格式错误）
 * @note 1. 仅支持三角形（3 顶点）和四边形（4 顶点）面，其他类型面会导致解析失败；
 *       2. 输出数组会自动扩容，无需提前初始化；
 *       3. 适用于需要自定义处理 OBJ 数据的场景（如二次加工、格式转换）
 */
bool DXLoadObjData(const string filename,DXVector4 &v_positions[],DXVector2 &v_tcoords[],DXVector4 &v_normals[],OBJFaceType &faces[],bool show_debug=false);

//+------------------------------------------------------------------+
//| 工具函数：加载 OBJ 模型并生成渲染用顶点/索引数据                  |
//| 模板约束：TVertex 必须包含 DXVector4 position（位置）、DXVector4 normal（法向量）、DXVector2 tcoord（纹理坐标）成员 |
//+------------------------------------------------------------------+
/**
 * @brief 从 Wavefront OBJ 文件加载模型，并生成 DirectX 渲染用的顶点和索引数据
 * @tparam TVertex 顶点数据类型（需包含 position、normal、tcoord 成员，如 DXVertex）
 * @param filename [in] OBJ 文件路径（绝对路径或相对路径）
 * @param vertices [out] 渲染用顶点数组（已整合位置、法向量、纹理坐标，可直接用于创建顶点缓冲）
 * @param indices [out] 渲染用索引数组（按三角形列表组织，可直接用于创建索引缓冲）
 * @param scale [in] 模型缩放系数（默认 1.0，>1 放大模型，<1 缩小模型，均匀缩放所有顶点）
 * @return bool - 操作结果：true 表示加载成功（数据解析+顶点/索引生成完成），false 表示失败（文件错误/数据不一致）
 * @note 核心流程：
 *       1. 调用 DXLoadObjData 解析 OBJ 原始数据；
 *       2. 检查面的索引一致性（顶点/纹理/法向量索引范围有效）；
 *       3. 整合顶点数据（根据索引关联位置、法向量、纹理坐标）；
 *       4. 生成三角形索引（四边形自动拆分为两个三角形）；
 *       5. 应用缩放系数到所有顶点位置
 */
template <typename TVertex>
bool DXLoadObjModel(const string filename,TVertex &vertices[],uint &indices[],float scale=1.0f);

//+------------------------------------------------------------------+
//| 工具函数：生成轴对齐立方体（AABB）的顶点和索引数据                |
//| 模板约束：TVertex 必须包含 DXVector4 position（位置）、DXVector4 normal（法向量）、DXVector2 tcoord（纹理坐标）成员 |
//+------------------------------------------------------------------+
/**
 * @brief 生成轴对齐立方体（AABB）的渲染用顶点和索引数据（6 个面，每个面 2 个三角形）
 * @tparam TVertex 顶点数据类型（需包含 position、normal、tcoord 成员，如 DXVertex）
 * @param from [in] 立方体左下角前顶点（3D 空间坐标，X/Y/Z 最小坐标）
 * @param to [in] 立方体右上角后顶点（3D 空间坐标，X/Y/Z 最大坐标，需满足 to.x>from.x、to.y>from.y、to.z>from.z）
 * @param vertices [out] 立方体顶点数组（共 24 个顶点，每个面 4 个顶点，避免顶点共享导致法向量错误）
 * @param indices [out] 立方体索引数组（共 36 个索引，6 个面×2 个三角形×3 个顶点）
 * @return bool - 操作结果：true 表示生成成功，false 表示失败（数组扩容失败）
 * @note 生成规则：
 *       1. 每个面独立 4 个顶点，法向量指向面的外侧（如左面法向量为 (-1,0,0)）；
 *       2. 纹理坐标按面设置，每个面的纹理范围为 (0,0) 到 (1,1)，支持纹理贴图；
 *       3. 索引按三角形列表组织，确保渲染时面的绕序正确（正面可见）
 */
template <typename TVertex>
bool DXComputeBox(const DXVector3 &from,const DXVector3 &to,TVertex &vertices[],uint &indices[]);

//+------------------------------------------------------------------+
//| 工具函数：生成球体的顶点和索引数据                                |
//| 模板约束：TVertex 必须包含 DXVector4 position（位置）、DXVector4 normal（法向量）、DXVector2 tcoord（纹理坐标）成员 |
//+------------------------------------------------------------------+
/**
 * @brief 生成球体的渲染用顶点和索引数据（通过经纬度网格逼近球面）
 * @tparam TVertex 顶点数据类型（需包含 position、normal、tcoord 成员，如 DXVertex）
 * @param radius [in] 球体半径（>0，决定球体大小）
 * @param tessellation [in] 细分精度（≥3，值越大球面越平滑，推荐 16~32，平衡性能与精度）
 * @param vertices [out] 球体顶点数组（数量为 (tessellation+1)×(2×tessellation+1)，按经纬度排列）
 * @param indices [out] 球体索引数组（数量为 6×tessellation×2×tessellation，三角形列表）
 * @return bool - 操作结果：true 表示生成成功，false 表示失败（数组扩容失败/细分精度无效）
 * @note 生成规则：
 *       1. 按纬度（垂直方向）和经度（水平方向）生成顶点，纬度范围 -90°~90°，经度范围 0°~360°；
 *       2. 顶点法向量与位置向量一致（从球心指向表面，确保光照正确）；
 *       3. 纹理坐标按经度（U 轴）和纬度（V 轴）映射，覆盖整个球面
 */
template <typename TVertex>
bool DXComputeSphere(float radius,uint tessellation,TVertex &vertices[],uint &indices[]);

//+------------------------------------------------------------------+
//| 工具函数：生成圆环体（Torus）的顶点和索引数据                      |
//| 模板约束：TVertex 必须包含 DXVector4 position（位置）、DXVector4 normal（法向量）、DXVector2 tcoord（纹理坐标）成员 |
//+------------------------------------------------------------------+
/**
 * @brief 生成圆环体（类似甜甜圈）的渲染用顶点和索引数据
 * @tparam TVertex 顶点数据类型（需包含 position、normal、tcoord 成员，如 DXVertex）
 * @param outer_radius [in] 外环半径（圆环中心到管中心的距离，>0）
 * @param inner_radius [in] 内环半径（管的半径，>0，需小于 outer_radius）
 * @param tessellation [in] 细分精度（≥3，值越大圆环越平滑，推荐 16~24）
 * @param vertices [out] 圆环体顶点数组（数量为 (tessellation+1)×(tessellation+1)）
 * @param indices [out] 圆环体索引数组（数量为 6×tessellation×tessellation，三角形列表）
 * @return bool - 操作结果：true 表示生成成功，false 表示失败（数组扩容失败/参数无效）
 * @note 生成规则：
 *       1. 先沿外环圆周生成管的中心轨迹，再沿管的圆周生成顶点；
 *       2. 顶点法向量垂直于管的表面，确保光照正确反映圆环形态；
 *       3. 纹理坐标按外环方向（U 轴）和管方向（V 轴）映射，避免纹理拉伸
 */
template <typename TVertex>
bool DXComputeTorus(float outer_radius,float inner_radius,uint tessellation,TVertex &vertices[],uint &indices[]);

//+------------------------------------------------------------------+
//| 工具函数：生成圆柱体的顶点和索引数据（调用圆台函数，上下半径相等）  |
//| 模板约束：TVertex 必须包含 DXVector4 position（位置）、DXVector4 normal（法向量）、DXVector2 tcoord（纹理坐标）成员 |
//+------------------------------------------------------------------+
/**
 * @brief 生成圆柱体的渲染用顶点和索引数据（上下底面半径相等的圆台）
 * @tparam TVertex 顶点数据类型（需包含 position、normal、tcoord 成员，如 DXVertex）
 * @param radius [in] 圆柱体底面/顶面半径（>0，上下半径相同）
 * @param height [in] 圆柱体高度（>0，沿 Y 轴方向的长度）
 * @param tessellation [in] 细分精度（≥3，值越大圆柱侧面越平滑，推荐 16）
 * @param vertices [out] 圆柱体顶点数组（包含侧面、顶面、底面顶点）
 * @param indices [out] 圆柱体索引数组（三角形列表，包含侧面和两个底面的索引）
 * @return bool - 操作结果：true 表示生成成功，false 表示失败（调用 DXComputeTruncatedCone 失败）
 * @note 内部调用 DXComputeTruncatedCone(radius, radius, height, tessellation, vertices, indices)，上下半径相等
 */
template <typename TVertex>
bool DXComputeCylinder(float radius,float height,uint tessellation,TVertex &vertices[],uint &indices[]);

//+------------------------------------------------------------------+
//| 工具函数：生成圆台（截头圆锥）的顶点和索引数据                    |
//| 模板约束：TVertex 必须包含 DXVector4 position（位置）、DXVector4 normal（法向量）、DXVector2 tcoord（纹理坐标）成员 |
//+------------------------------------------------------------------+
/**
 * @brief 生成圆台（上下底面半径不同的圆锥体截断形态）的渲染用顶点和索引数据
 * @tparam TVertex 顶点数据类型（需包含 position、normal、tcoord 成员，如 DXVertex）
 * @param radius_top [in] 顶面半径（≥0，为 0 时退化为圆锥体的顶面顶点）
 * @param radius_bottom [in] 底面半径（>0，需大于 radius_top）
 * @param height [in] 圆台高度（>0，沿 Y 轴方向的长度，顶面 Y 坐标为 height/2，底面为 -height/2）
 * @param tessellation [in] 细分精度（≥3，值越大侧面越平滑，推荐 16）
 * @param vertices [out] 圆台顶点数组（包含侧面、顶面、底面顶点）
 * @param indices [out] 圆台索引数组（三角形列表，包含侧面和两个底面的索引）
 * @return bool - 操作结果：true 表示生成成功，false 表示失败（数组扩容失败/参数无效）
 * @note 生成规则：
 *       1. 侧面顶点按环形排列，法向量沿侧面切线方向，确保光照平滑；
 *       2. 顶面和底面为圆形，法向量分别为 (0,1,0) 和 (0,-1,0)；
 *       3. 纹理坐标侧面按高度（V 轴）和环形（U 轴）映射，底面/顶面按极坐标映射
 */
template <typename TVertex>
bool DXComputeTruncatedCone(float radius_top,float radius_bottom,float height,uint tessellation,TVertex &vertices[],uint &indices[]);

//+------------------------------------------------------------------+
//| 工具函数：生成圆锥体的顶点和索引数据（调用圆台函数，顶面半径为 0）  |
//| 模板约束：TVertex 必须包含 DXVector4 position（位置）、DXVector4 normal（法向量）、DXVector2 tcoord（纹理坐标）成员 |
//+------------------------------------------------------------------+
/**
 * @brief 生成圆锥体的渲染用顶点和索引数据（顶面半径为 0 的圆台）
 * @tparam TVertex 顶点数据类型（需包含 position、normal、tcoord 成员，如 DXVertex）
 * @param radius [in] 圆锥体底面半径（>0）
 * @param height [in] 圆锥体高度（>0，沿 Y 轴方向的长度，顶点 Y 坐标为 height/2，底面为 -height/2）
 * @param tessellation [in] 细分精度（≥3，值越大侧面越平滑，推荐 16）
 * @param vertices [out] 圆锥体顶点数组（包含侧面和底面顶点，顶面为单个顶点）
 * @param indices [out] 圆锥体索引数组（三角形列表，包含侧面和底面的索引）
 * @return bool - 操作结果：true 表示生成成功，false 表示失败（调用 DXComputeTruncatedCone 失败）
 * @note 内部调用 DXComputeTruncatedCone(0, radius, height, tessellation, vertices, indices)，顶面半径为 0
 */
template <typename TVertex>
bool DXComputeCone(float radius,float height,uint tessellation,TVertex &vertices[],uint &indices[]);

//+------------------------------------------------------------------+
//| 工具函数：生成数据驱动的曲面（高度场）顶点和索引数据              |
//| 模板约束：TVertex 必须包含 DXVector4 position（位置）、DXVector4 normal（法向量）、DXVector2 tcoord（纹理坐标）成员 |
//+------------------------------------------------------------------+
/**
 * @brief 根据二维数值数据生成高度场曲面的渲染用顶点和索引数据（适用于数据可视化）
 * @tparam TVertex 顶点数据类型（需包含 position、normal、tcoord 成员，如 DXVertex）
 * @param data [in] 二维数据数组（按 [行][列] 存储，值对应曲面在该点的高度，需为一维数组模拟二维结构）
 * @param data_width [in] 数据宽度（列数，≥2，对应曲面 X 方向的采样点数量）
 * @param data_height [in] 数据高度（行数，≥2，对应曲面 Z 方向的采样点数量）
 * @param data_range [in] 数据值缩放范围（控制曲面高度差，≤0 时自动使用数据的最大值-最小值）
 * @param from [in] 曲面左下角起始坐标（3D 空间 X/Z 范围，Y 为高度基准）
 * @param to [in] 曲面右上角结束坐标（3D 空间 X/Z 范围，与 from 共同定义曲面在 X/Z 方向的大小）
 * @param texture_size [in] 纹理缩放比例（2D 向量，控制纹理在曲面上的重复次数，如 (2,2) 表示重复 2 次）
 * @param two_sided [in] 双面渲染标志（true=曲面正反面均可见，false=仅正面可见）
 * @param use_normals [in] 法向量计算标志（true=自动计算顶点法向量，支持光照；false=法向量为 0 向量）
 * @param vertices [out] 曲面顶点数组（数量为 data_width×data_height×(two_sided?2:1)）
 * @param indices [out] 曲面索引数组（数量为 6×(data_width-1)×(data_height-1)×(two_sided?2:1)，三角形列表）
 * @return bool - 操作结果：true 表示生成成功，false 表示失败（数据尺寸无效/数组扩容失败）
 * @note 核心流程：
 *       1. 统计数据的最大值/最小值，结合 data_range 计算高度缩放系数；
 *       2. 按 X/Z 方向的采样点生成顶点，Y 坐标由数据值映射得到；
 *       3. （可选）计算顶点法向量（通过相邻顶点的叉乘实现）；
 *       4. 生成三角形索引（每个数据网格单元拆分为两个三角形）；
 *       5. （可选）生成背面顶点和索引（two_sided=true 时），并反转法向量
 */
template <typename TVertex>
bool DXComputeSurface(double &data[],uint data_width,uint data_height,double data_range,
                      const DXVector3 &from,const DXVector3 &to,DXVector2 &texture_size,
                      bool two_sided,bool use_normals,
                      TVertex &vertices[],uint &indices[]);

//+------------------------------------------------------------------+
//| 工具函数：生成 Jet 配色方案颜色（Matlab 风格，适用于科学数据可视化）|
//+------------------------------------------------------------------+
/**
 * @brief 根据 [0,1] 范围的数值生成 Jet 配色方案的颜色（蓝→青→黄→红渐变）
 * @param value [in] 输入数值（范围 [0,1]，值越小越偏蓝，值越大越偏红）
 * @param cout [out] 输出颜色（RGBA 格式，A=1.0，颜色随 value 线性渐变）
 * @note 配色规则（value 对应颜色）：
 *       - 0.0~0.25：深蓝→浅蓝；0.25~0.5：浅蓝→青；0.5~0.75：青→黄；0.75~1.0：黄→红
 *       - 自动限制 value 范围（<0 按 0 处理，>1 按 1 处理），避免颜色异常
 */
void DXComputeColorJet(const float value,DXColor &cout);

//+------------------------------------------------------------------+
//| 工具函数：生成冷到热配色方案颜色（适用于温度、强度等数据可视化）    |
//+------------------------------------------------------------------+
/**
 * @brief 根据 [0,1] 范围的数值生成冷到热配色方案的颜色（蓝→绿→黄→红渐变）
 * @param value [in] 输入数值（范围 [0,1]，值越小越偏冷色（蓝），值越大越偏暖色（红））
 * @param cout [out] 输出颜色（RGBA 格式，A=1.0，颜色随 value 线性渐变）
 * @note 配色规则（value 对应颜色）：
 *       - 0.0~0.5：蓝→绿；0.5~1.0：绿→黄→红
 *       - 自动限制 value 范围（<0 按 0 处理，>1 按 1 处理），确保颜色过渡平滑
 */
void DXComputeColorColdToHot(const float value,DXColor &cout);

//+------------------------------------------------------------------+
//| 工具函数：生成红到绿配色方案颜色（适用于二元对比数据可视化）        |
//+------------------------------------------------------------------+
/**
 * @brief 根据 [0,1] 范围的数值生成红到绿配色方案的颜色（红→黄→绿渐变）
 * @param value [in] 输入数值（范围 [0,1]，值越小越偏红，值越大越偏绿）
 * @param cout [out] 输出颜色（RGBA 格式，A=1.0，颜色随 value 非线性渐变）
 * @note 配色规则（value 对应颜色）：
 *       - 0.0~0.5：纯红→黄（R=1.0，G 从 0.01 增至 0.95）；
 *       - 0.5~1.0：黄→深绿（R 从 1.0 减至 0.1，G 从 0.95 微调至 0.6）；
 *       - 自动限制 value 范围（<0 按 0 处理，>1 按 1 处理），突出中间值（黄色）
 */
void DXComputeColorRedToGreen(const float value,DXColor &cout);

//+------------------------------------------------------------------+